Repository navigation
Fix incorrect GPU partitioning and ordering metadata [databricks] - #16620
Conversation
Map partitioning attributes through direct aliases rather than projection output positions. Projecting id % 10 AS k before id AS v otherwise falsely advertised range partitioning on k. Cached scans preserved that claim, so Spark omitted the grouping shuffle and returned per-partition results. Preserve identity and direct CPU/GPU attribute aliases, and let existing reference filtering discard unsupported computed-column partitioning. Keep aggregate and other operator implementations unchanged. Add a compact projection metadata regression and the 100-row cached aggregation reproduction with AQE both enabled and disabled. The new integration regression fails the original jar with CPU 10 / GPU 100 rows. Validation: 25 Scala and 9 Java tests passed on Spark 3.5.5. The new integration regression passed with default cache and PCBS, AQE on/off; full PCBS cache suite: 625 passed, 4 large-data skips. Standalone shell reproductions and controls returned exact results with both serializers. SQL compile/package checks passed on Spark 3.3.4 and 4.0.1/Scala 2.13. Status: READY FOR REVIEW. Verification evidence is retained outside the repository under /data/pcbs-oom/project-partitioning-fix, as requested. Signed-off-by: Robert (Bobby) Evans <bobby@apache.org>
Use the installed Spark version's partitioning and ordering traits instead of maintaining a separate attribute remapping algorithm. Keep original CPU expressions for metadata matching in a second constructor parameter list, following GPU sort and shuffle, so Spark does not treat them as executable expressions. Preserve those expressions across child replacement and projection pruning. Compare metadata directly with native Spark for aliases, computed expressions, ordering prefixes, null ordering and candidate limits. Keep the cached aggregate regression for both AQE settings. Signed-off-by: Robert (Bobby) Evans <bobby@apache.org>
Remove canonicalized-equality assertions that cannot establish anything about second-parameter-list CPU metadata. Keep the direct expression and copy-preservation checks. Collect cached query results before unpersisting in a finally block for each CPU and GPU session, including when query execution fails. Signed-off-by: Robert (Bobby) Evans <bobby@apache.org>
Signed-off-by: Robert (Bobby) Evans <bobby@apache.org>
[High impact] No new blocking issue was found in the updated cache tests. SummaryThe PR replaces positional partitioning claims with Spark’s alias-aware partitioning and ordering logic. CPU expressions stay separate from GPU execution expressions and survive plan copies and projection pruning.
Diagram%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Original CPU expressions] --> C[Spark alias-aware metadata]
B[GPU expressions] --> D[GPU execution]
C --> E[Output partitioning and ordering]
E --> F[Required shuffles and sorts]
D --> G[Query results]
F --> G
Reviews (8) · Last reviewed commit: "Rework" · Reviewed by Greptile |
There was a problem hiding this comment.
🟡 Changes recommended
Required performance results or a linked validation issue are missing for changes that can alter shuffle and sort execution.
1 open issue
What changed in this PR
Fixes incorrect partitioning and ordering metadata in GPU projections and aggregates, preventing Spark from skipping required shuffles and returning incorrect results.
Changes:
- Reuses Spark’s version-specific alias-aware metadata logic while keeping CPU metadata expressions separate from GPU execution expressions.
- Preserves metadata through plan copies, projection pruning, and Delta rewrites.
- Adds metadata parity tests and a cached-aggregation regression with adaptive execution enabled and disabled.
Shim validation remains INCOMPLETE: the repository snapshot was unavailable, and current-head/base shim-compatibility and shim-related-prs reports could not be verified. The reported focused tests do not establish full affected-profile coverage.
| File | Description |
|---|---|
| tests/src/test/scala/org/apache/spark/sql/rapids/ProjectExprSuite.scala | Tests alias metadata, ordering, candidate limits, and rewrites. |
| tests/src/test/scala/com/nvidia/spark/rapids/ProjectSplitRetrySuite.scala | Updates projection construction. |
| tests/src/test/scala/com/nvidia/spark/rapids/HashAggregatesSuite.scala | Updates aggregate construction. |
| sql-plugin/src/main/spark340/scala/org/apache/spark/sql/rapids/execution/GpuBroadcastNestedLoopJoinExec.scala | Updates join projection construction. |
| sql-plugin/src/main/spark340/scala/com/nvidia/spark/rapids/shims/GpuAliasAwareExecNodeShims.scala | Adds Spark 3.4+ alias-aware metadata adapters. |
| sql-plugin/src/main/spark332db/scala/org/apache/spark/sql/rapids/GpuFileFormatWriter.scala | Updates write projection construction. |
| sql-plugin/src/main/spark330db/scala/org/apache/spark/sql/rapids/execution/GpuBroadcastNestedLoopJoinExec.scala | Updates Databricks join projections. |
| sql-plugin/src/main/spark330/scala/org/apache/spark/sql/rapids/GpuFileFormatWriter.scala | Updates write projection construction. |
| sql-plugin/src/main/spark330/scala/org/apache/spark/sql/rapids/execution/GpuBroadcastNestedLoopJoinExec.scala | Updates Spark 3.3 join projections. |
| sql-plugin/src/main/spark330/scala/com/nvidia/spark/rapids/shims/GpuAliasAwareExecNodeShims.scala | Adds Spark 3.3 alias-aware metadata adapters. |
| sql-plugin/src/main/scala/org/apache/spark/sql/rapids/execution/GpuRangeBoundaryPlan.scala | Preserves metadata during projection pruning. |
| sql-plugin/src/main/scala/org/apache/spark/sql/rapids/execution/GpuHashJoin.scala | Updates generated join projections. |
| sql-plugin/src/main/scala/com/nvidia/spark/rapids/window/GpuWindowExecMeta.scala | Updates generated window projections. |
| sql-plugin/src/main/scala/com/nvidia/spark/rapids/GpuAliasAwareOutputExpressions.scala | Converts GPU aliases for metadata matching. |
| sql-plugin/src/main/scala/com/nvidia/spark/rapids/GpuAggregateExec.scala | Uses Spark metadata logic with retained CPU expressions. |
| sql-plugin/src/main/scala/com/nvidia/spark/rapids/basicPhysicalOperators.scala | Replaces positional metadata mapping and preserves projection metadata. |
| integration_tests/src/main/python/cache_test.py | Adds cached-projection aggregation regression. |
| delta-lake/delta-spark400db173/src/main/scala/com/nvidia/spark/rapids/delta/DeltaSpark400DB173Provider.scala | Preserves metadata during Databricks Delta rewrites. |
| delta-lake/delta-spark332db/src/main/scala/com/databricks/sql/transaction/tahoe/rapids/GpuOptimisticTransactionBase.scala | Updates transaction projection construction. |
| delta-lake/delta-spark330db/src/main/scala/com/databricks/sql/transaction/tahoe/rapids/GpuOptimisticTransactionBase.scala | Updates transaction projection construction. |
| delta-lake/common/src/main/delta-io/scala/org/apache/spark/sql/delta/rapids/AbstractGpuOptimisticTransactionBase.scala | Updates shared Delta projection construction. |
| delta-lake/common/src/main/delta-33x-42x/scala/com/nvidia/spark/rapids/delta/common/DeltaProviderBase.scala | Preserves metadata through Delta pruning and merging. |
| delta-lake/common/src/main/db-350db143-410db183/scala/com/databricks/sql/transaction/tahoe/rapids/GpuOptimisticTransactionBaseCommon.scala | Updates shared Databricks projection construction. |
Review effort: Balanced
Signed-off-by: Robert (Bobby) Evans <bobby@apache.org>
Signed-off-by: Robert (Bobby) Evans <bobby@apache.org>
|
build |
amahussein
left a comment
There was a problem hiding this comment.
LGTME
A couple of small inline requests.
- A test could cover beyond range partitioning.
- add a couple of comments to define the contract for the new cpuProjectList argument in the GpuProjectExec.
|
Signed-off-by: Robert (Bobby) Evans <bobby@apache.org>
… contract Signed-off-by: Robert (Bobby) Evans <bobby@apache.org>
Signed-off-by: Robert (Bobby) Evans <bobby@apache.org>
Signed-off-by: Robert (Bobby) Evans <bobby@apache.org>
|
@amahussein your changes should have both been addressed. As for #16690, no I have not done anything with it, but the fix went into 4.2.1, 4.3.X and main. It is mostly a question of do we want to be bug for bug compatible with Apache Spark. Generally I would say no, but because this bug exists only for a single version of Spark and the cost to "fix" it is a 200 line patch in Apache spark, that I don't really know 100% what it would take for us to fix it here means I don't think it is worth doing for 4.2.0. If you disagree we can discuss it in a follow on PR as this fixes data corruption, and I really would like to get it in. |
|
build |
|
The build is failing due to a merge conflict in a test. Working on it now... |
Signed-off-by: Robert (Bobby) Evans <bobby@apache.org>
|
build |
|
build |

GPU projections and aggregations could report partitioning by output position and pass ordering through unchanged. For a query like
The result is a correct 10 rows, but if you persist the result in between the
selectExprand thegroupBylikeon the GPU, this causes the ordering and partitioning from the
GpuProjectto be used and the shuffle before the partial aggregation is not inserted, resulting in 100 rows being returned. This is because the existing code only goes off of position, not inspecting the actual expressions.This fixes the issue by reusing the installed Spark version’s alias-aware metadata logic for GPU projections and aggregates. It keeps CPU expressions for metadata matching separate from GPU execution expressions, and preserves them through plan copies and projection pruning. If those are not available a conservative fallback happens. This will not be perfect, because the ordering and partitioning can include CPU expressions in them that will not match the GPU expressions, but it will not corrupt the data. In the worst case we may end up with more shuffle operations than before.
Validation:
This appears to have been caused by #14164 and #14264. The first one put in the logic and the second one made it so more operators adopted it. The original logic really only worked for union, and even there I am not 100% sure that it always did.
I will try to run some performance tests, but as this is a data corruption fix it is not strictly required, and if the issue is hit, we will take a performance hit in order to produce the correct result (as a shuffle must be inserted to make the plan correct).
This also adds the internal, test-only
spark.rapids.sql.test.forceNonAstsetting to force selected expression classes out of AST while retaining GPU expression support. It defaults to an empty list and takes effect only when test mode is enabled.AI-assisted implementation and testing.
Checklists
Documentation
Testing
Performance